using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections.Specialized;

namespace Speedball2 {
  public class Entity {
    public int _offset;
    public int _offsetRam;

    public int _pGfx;
    public int _pFunctionDraw;
    public int _pThink;
    public int _pOpcodes;
    public int _width;
    public int _height;
    public Point _terrainXY;
    public Point _velocityXY;
    public int _opcodeIndex;
    public int _dir;
    public int _timer;
    public int _spriteIndex;
    public BitVector32 _flags;
    public int _field_23_b;
    public Point _originXY;

    public int _bit0_isBusy;
    public int _bit1_isJumping;
    public int _bit2_isThrowing;
    public int _bit3_IsTeam1_IsElectrobounced;
    public int _bit4_isFalling;
    public int _bit5_isTackling_isBlocking;
    public int _bit6_isOffscreen;
    public int _bit7_isInvisible;

    public Entity (byte [] bytes, int offset) {
      _offset = offset;
      _offsetRam = Game.AtariDiskToRam (offset);

      _pGfx = (int) bytes.ReadLong (offset);
      offset += 4;
      _pFunctionDraw = (int) bytes.ReadLong (offset);
      offset += 4;
      _pThink = (int) bytes.ReadLong (offset);
      offset += 4;
      _pOpcodes = (int) bytes.ReadLong (offset);
      offset += 4;
      _width = bytes.ReadWord (offset);
      offset += 2;
      _height = bytes.ReadWord (offset);
      offset += 2;
      _terrainXY = new Point (bytes.ReadWord (offset), bytes.ReadWord (offset + 2));
      offset += 4;
      _velocityXY = new Point (bytes.ReadWord (offset), bytes.ReadWord (offset + 2));
      offset += 4;
      _opcodeIndex = bytes.ReadWord (offset);
      offset += 2;
      _dir = bytes.ReadByte (offset++);
      _timer = bytes.ReadByte (offset++);
      _spriteIndex = bytes.ReadWord (offset);
      offset += 2;

      int flags = bytes.ReadByte (offset++);
      _bit0_isBusy = BitVector32.CreateMask ();
      _bit1_isJumping = BitVector32.CreateMask (_bit0_isBusy);
      _bit2_isThrowing = BitVector32.CreateMask (_bit1_isJumping);
      _bit3_IsTeam1_IsElectrobounced = BitVector32.CreateMask (_bit2_isThrowing);
      _bit4_isFalling = BitVector32.CreateMask (_bit3_IsTeam1_IsElectrobounced);
      _bit5_isTackling_isBlocking = BitVector32.CreateMask (_bit4_isFalling);
      _bit6_isOffscreen = BitVector32.CreateMask (_bit5_isTackling_isBlocking);
      _bit7_isInvisible = BitVector32.CreateMask (_bit6_isOffscreen);
      _flags [_bit0_isBusy] = ((flags >> 0) & 1) == 1;
      _flags [_bit1_isJumping] = ((flags >> 1) & 1) == 1;
      _flags [_bit2_isThrowing] = ((flags >> 2) & 1) == 1;
      _flags [_bit3_IsTeam1_IsElectrobounced] = ((flags >> 3) & 1) == 1;
      _flags [_bit4_isFalling] = ((flags >> 4) & 1) == 1;
      _flags [_bit5_isTackling_isBlocking] = ((flags >> 5) & 1) == 1;
      _flags [_bit6_isOffscreen] = ((flags >> 6) & 1) == 1;
      _flags [_bit7_isInvisible] = ((flags >> 7) & 1) == 1;

      _field_23_b = bytes.ReadByte (offset++);
      _originXY = new Point (bytes.ReadWordSigned (offset), bytes.ReadWordSigned (offset + 2));
      offset += 4;

      if (_timer != 0)
        throw new Exception ();

      if ((_width % 8) != 0)
        throw new Exception ();
      if (_width != _height)

        //Console.WriteLine (string.Format ("{0}/{1}", _width, _height));
        if ((offset - _offset) != Size)
          throw new Exception ();
    }

    public void ExecuteOpcode (Match match) {
      //if (match.goal)
      //  if (this == match.PlayersTeam2 [7])
      //    Console.WriteLine ("{0:X8} [{1}]", _pOpcodes, _opcodeIndex);
      if (Bit7_IsInvisible)
        return;
      if (_opcodeIndex < 0)
        throw new Exception ();
      _spriteIndex = Game.AtariDisk.ReadWord (Game.AtariRamToDisk (_pOpcodes + _opcodeIndex * 2));
      if (_spriteIndex >= 0x8000)
        throw new Exception ();
      _opcodeIndex++;
      int nextOpcode = Game.AtariDisk.ReadWord (Game.AtariRamToDisk (_pOpcodes + _opcodeIndex * 2));
      if (nextOpcode == 0xFFFF) {
        _opcodeIndex = 0;
      }
      else if (nextOpcode == 0xFFFE) {
        _opcodeIndex--;
      }
      else if (nextOpcode == 0xFFFB) {
        // injured
        _velocityXY = Point.Empty;
        _opcodeIndex--;
        Bit1_IsJumping = false;
        Bit2_IsThrowing = false;
        Bit4_IsFalling = false;
        Bit5_IsTackling_IsBlocking = false;
        _pThink = 0xF344;
        Player player = GetPlayer;
        if (player._attributes._health != 0) {
          _pOpcodes = 0x5B72;
          _opcodeIndex = 0;
          Bit0_IsBusy = false;
        }
        else
          player.sub_F6DC_InjuredInitMedic (match);
      }
      else if (nextOpcode == 0xFFFD) {
        _pThink = 0xF212;
        if (this != match.EntityHoldingBall)
          if (!Bit2_IsThrowing)
            _pThink = 0xEB7A;
        Bit0_IsBusy = false;
        Bit1_IsJumping = false;
        Bit2_IsThrowing = false;
        Bit4_IsFalling = false;
        Bit5_IsTackling_IsBlocking = false;
      }
      else if (nextOpcode == 0xFFFC) {
        Bit7_IsInvisible = true;
        _opcodeIndex = 0;
      }
    }

    public void InitItemCoins () {
      CalcItemCoinsNewTerrainXY ();
      Bit7_IsInvisible = true;
      _timer = 32 + Game.myRandom.NextByte (31);
    }

    private void CalcItemCoinsNewTerrainXY () {
      Game.ReferenceEquals (null, null);
      int x = 80 + Game.myRandom.NextWord (224); // &7*32
      if (!IsItem1 && !IsItem3)
        x += 256;
      int y = 80 + Game.myRandom.NextWord (480); // &15*32
      if (IsItem34567)
        y += 512;
      _terrainXY = new Point (x, y);
    }

    public void InitItemToken (GameTypeEnum gameType) {
      CalcItemTokenNewTerrainXY ();
      Bit7_IsInvisible = true;
      int spriteIndex;
      for (; ; ) {
        long random = Game.myRandom.Next ();
        _timer = 128 + (((int) random) & 127);
        random >>= 16;
        spriteIndex = 55 + (((int) random) & 15);
        if (spriteIndex == 67)
          continue;
        if (spriteIndex == 56)
          if (gameType != GameTypeEnum.TwoPlayers)
            continue;
        if (spriteIndex > 67)
          continue;
        break;
      }
      _spriteIndex = spriteIndex;
    }

    private void CalcItemTokenNewTerrainXY () {
      int x = 80 + Game.myRandom.NextWord (480); // &15*32
      int y = 80 + Game.myRandom.NextWord (480); // &15*32
      if (!IsItem5)
        y += 512;
      _terrainXY = new Point (x, y);
    }

    public void InitItemEquipment (Match match) {
      if (this != match.EntityItemEquipment)
        throw new Exception ();
      CalcItemEquipmentNewTerrainXY ();
      Bit7_IsInvisible = true;
      long random = Game.myRandom.Next ();
      _timer = (((int) random) & 255);
      random >>= 16;
      _spriteIndex = 80 + (((int) random) & 7);
      match._equipmentOnFloorSpriteIndex = _spriteIndex;
      match._equipmentNbPickupRemaining = 2;
    }

    private void CalcItemEquipmentNewTerrainXY () {
      int x = 80 + Game.myRandom.NextWord (480); //&15*32
      int y = 80 + Game.myRandom.NextWord (992); // &31*32
      _terrainXY = new Point (x, y);
    }

    public void InitScoreMultiplier (int spriteIndex) {
      _spriteIndex = spriteIndex;
      Bit7_IsInvisible = true;
    }

    public void MoveAndHandleWallsAndBounce (int margin) {
      if (Bit7_IsInvisible)
        return;
      bool bounce = false;
      int velocityX = _velocityXY.X;
      if ((640 - margin) < _terrainXY.X) {
        bounce = true;
        _terrainXY.X = 640 - margin;
      }
      else if (margin > _terrainXY.X) {
        _terrainXY.X = margin;
        bounce = true;
      }
      if (bounce) {
        if (IsBall) {
          velocityX = -velocityX;
          _dir = Match.GetMirrorXFromDir (_dir);
        }
      }
      // Else1
      _velocityXY.X = velocityX;
      _terrainXY.X += velocityX;

      bounce = false;
      int velocityY = _velocityXY.Y;
      if ((1152 - margin) < _terrainXY.Y) {
        bounce = true;
        _terrainXY.Y = 1152 - margin;
      }
      else if (margin > _terrainXY.Y) {
        _terrainXY.Y = margin;
        bounce = true;
      }
      if (bounce) {
        if (IsBall) {
          velocityY = -velocityY;
          _dir = Match.GetMirrorYFromDir (_dir);
          _timer -= _timer / 2;
        }
      }
      _velocityXY.Y = velocityY;
      _terrainXY.Y += velocityY;
    }

    public void UpdateBallVelocity (Match match) {
      if (!IsBall)
        throw new Exception ();

      if (!match.Bit7_IsClockPaused)
        if (match.EntityHoldingBall != null)
          return;
      if (match.Bit2_IsBallInScoreMultiplier)
        return;
      if (_velocityXY == Point.Empty) {
        Bit3_IsTeam1_IsElectrobounced = false; // no more electrobounced
        return;
      }
      if (_velocityXY.Y != 0) {
        if (_terrainXY.Y < 48)
          return;
        if (_terrainXY.Y > 1104)
          return;
      }

      if (_timer != 0) {
        if (match.BallSpeedUnk <= _timer) {
          if (_timer != 0)
            _timer--;
          return;
        }
        else
          match.BallSpeedUnk = (match.BallSpeedUnk / 2) + (match.BallSpeedUnk / 4);
      }

      int velocityX = _velocityXY.X;
      if (velocityX != 0) {
        if (velocityX > 0)
          velocityX--;
        else
          velocityX++;
        if ((velocityX != 0) || (_spriteIndex == 0))
          _velocityXY.X = velocityX;
      }

      int velocityY = _velocityXY.Y;
      if (velocityY != 0) {
        if (velocityY > 0)
          velocityY--;
        else
          velocityY++;
        if ((velocityY != 0) || (_spriteIndex == 0))
          _velocityXY.Y = velocityY;
      }

      if (_timer != 0)
        _timer--;
    }

    public void UpdateDistanceBallToPlayers (Match match, Player [] players) {
      // currrent entity is the ball or the ball owner
      if ((this != match.EntityBall) && (this != match.EntityHoldingBall))
        throw new Exception ();

      foreach (Player player in players) {
        Point p2;
        player._distanceToBall = player.CalcVector (this._terrainXY, out p2);
        player.SetOnOffScreen (match);
      }
    }

    public void SetOnOffScreen (Match match) {
      Bit6_IsOffscreen = true;
      if (Bit7_IsInvisible)
        return;
      if ((_terrainXY.Y - match.ScrollXY.Y) < 0)
        return;
      if ((_terrainXY.Y - match.ScrollXY.Y) > 184)
        return;
      if ((_terrainXY.X - match.ScrollXY.X) < 0)
        return;
      if ((_terrainXY.X - match.ScrollXY.X) > 320)
        return;
      Bit6_IsOffscreen = false;
    }

    public void Draw (Graphics g, Point scrollXY) {
      if (Bit7_IsInvisible)
        return;

      int x = _originXY.X + _terrainXY.X - scrollXY.X - (_width / 2);
      int y = _originXY.Y + _terrainXY.Y - scrollXY.Y - (_height / 2);

      //int height = _height;
      //if (_width != _height)
      //  height = _width;
      int spriteIndex = _spriteIndex;
      //int pGfx;
      if (IsBall) {
        if (Bit3_IsTeam1_IsElectrobounced) // electrobounced
          spriteIndex += 11;
      }
      //pGfx = _pGfx + spriteIndex * _width * height / 2;
      //if (IsPlayerOrMedic)
      //  g.DrawImage (BankManager.GetBitmap (pGfx, Bit3_IsTeam1_IsElectrobounced), x, y);
      //else
      //  g.DrawImage (BankManager.GetBitmap (pGfx), x, y);
      BankEnum bank;
      if (_pGfx == 0x193EA)
        bank = BankEnum.Entities;
      else if (_pGfx == 0x1C0EA) {
        if ((IsPlayerOrMedic) && (Bit3_IsTeam1_IsElectrobounced))
          bank = BankEnum.PlayersSprBlue;
        else
          bank = BankEnum.PlayersSpr;
      }
      // big ball
      else if (_pGfx == 0x2C4EA) {
        bank = BankEnum.PlayersSpr;
        spriteIndex += 130;
      }
      else if (_pGfx == 0x537EA)
        bank = BankEnum.PitchDat;
      else
        throw new Exception ();
      List<Bitmap> bitmaps = SpriteManager.GetBank (bank);
      if (SpriteManager.Version == VersionEnum.Amiga) {
        // hack for missing electrobouncer and double dome in Atari
        if (bank == BankEnum.PlayersSpr)
          if (spriteIndex >= 0x87)
            if (!IsElectrobounce)
              spriteIndex++;
        // hack for missing token in amiga
        if (bank == BankEnum.Entities)
          if (spriteIndex == 67) // unused token
            throw new Exception ();
          else if (spriteIndex >= 68)
            spriteIndex--;
      }
      g.DrawImage (bitmaps [spriteIndex], x, y);
      //g.DrawStringOutlinedCentered (string.Format ("0x{0:X}", pGfx), Game.Font, Brushes.White, Brushes.Black, x + _width / 2, y + _height / 2 - 8);
      //g.DrawStringOutlinedCentered (spriteIndex.ToString (), Game.Font, Brushes.White, Brushes.Black, x + _width / 2, y + _height / 2 + 8);
      // todo: memorize replay

      if (Game._debug) {
        //if (this is Player) {
        //  g.DrawStringOutlinedCentered (((Player) this)._index.ToString (), Game.Font, Brushes.White, Brushes.Black, x, y);
        //}
        //if (bank == BankEnum.Entities) {
        //  g.DrawStringOutlinedCentered (_spriteIndex.ToString (), Game.Font, Brushes.White, Brushes.Black, x, y);
        //}
      }
    }

    public bool Bit0_IsBusy {
      get {
        return (_flags [_bit0_isBusy]);
      }
      set {
        _flags [_bit0_isBusy] = value;
      }
    }

    public bool Bit1_IsJumping {
      get {
        return (_flags [_bit1_isJumping]);
      }
      set {
        _flags [_bit1_isJumping] = value;
      }
    }

    public bool Bit2_IsThrowing {
      get {
        return (_flags [_bit2_isThrowing]);
      }
      set {
        _flags [_bit2_isThrowing] = value;
      }
    }

    // true, 1: blue
    // false, 0: red
    public bool Bit3_IsTeam1_IsElectrobounced {
      get {
        //return ((SideEnum) (_flags [_bit3_Side] ? 1 : 0));
        return (_flags [_bit3_IsTeam1_IsElectrobounced]);
      }
      set {
        //bool side = (value == SideEnum.Blue_1);
        //_flags [_bit3_Side] = side;
        _flags [_bit3_IsTeam1_IsElectrobounced] = value;
      }
    }

    public bool Bit4_IsFalling {
      get {
        return (_flags [_bit4_isFalling]);
      }
      set {
        _flags [_bit4_isFalling] = value;
      }
    }

    public bool Bit5_IsTackling_IsBlocking {
      get {
        return (_flags [_bit5_isTackling_isBlocking]);
      }
      set {
        _flags [_bit5_isTackling_isBlocking] = value;
      }
    }

    public bool Bit6_IsOffscreen {
      get {
        return (_flags [_bit6_isOffscreen]);
      }
      set {
        _flags [_bit6_isOffscreen] = value;
      }
    }

    public bool Bit7_IsInvisible {
      get {
        return (_flags [_bit7_isInvisible]);
      }
      set {
        _flags [_bit7_isInvisible] = value;
      }
    }

    public Point TerrainXY {
      get {
        return (_terrainXY);
      }
      set {
        _terrainXY = value;
      }
    }

    public bool IsBall {
      get {
        return (_offset == Game.AtariRamToDisk (0x4812));
      }
    }

    public bool IsPlayerOrMedic {
      get {
        return ((_offset >= Game.AtariRamToDisk (0x4BB2)) && (_offset < Game.AtariRamToDisk (0x5406)));
      }
    }

    public bool IsElectrobounce {
      get {
        return ((_offset >= Game.AtariRamToDisk (0x4B5E)) && (_offset < Game.AtariRamToDisk (0x4BB2)));
      }
    }

    public bool IsItem1 {
      get {
        return (_offset == Game.AtariRamToDisk (0x488A));
      }
    }

    public bool IsItem3 {
      get {
        return (_offset == Game.AtariRamToDisk (0x48DA));
      }
    }

    public bool IsItem5 {
      get {
        return (_offset == Game.AtariRamToDisk (0x492A));
      }
    }

    public bool IsItem34567 {
      get {
        return (_offset > Game.AtariRamToDisk (0x48B2));
      }
    }

    public Player GetPlayer {
      get {
        if (this is Player)
          return ((Player) this);
        else
          throw new Exception ();
      }
    }

    public void sub_D214_ExecuteOpcodeCoins (Match match) {
      if (!match.Bit7_IsClockPaused) {
        if (Bit7_IsInvisible) {
          _timer--;
          if (_timer != 0)
            return;
          if (match._creditsEarnedTeam1 >= match._maxCreditsEarnableTeam1) {
            if (match.GameType != GameTypeEnum.TwoPlayers)
              return;
            if (match._creditsEarnedTeam2 >= match._maxCreditsEarnableTeam2)
              return;
          }
          Bit7_IsInvisible = false;
        }
        if (_pThink == 0xFC3C)
          sub_FC3C_Think_Coins (match);
        else
          throw new Exception ();
      }
      ExecuteOpcode (match);
    }

    public DirBits GetDirBitsToTargetSimple (Point targetXY) {
      DirBits dirBits = new DirBits ();
      if (targetXY.X != _terrainXY.X)
        if (targetXY.X > _terrainXY.X)
          dirBits.Bit3_IsRight = true;
        else
          dirBits.Bit2_IsLeft = true;
      if (targetXY.Y != _terrainXY.Y)
        if (targetXY.Y > _terrainXY.Y)
          dirBits.Bit1_IsDown = true;
        else
          dirBits.Bit0_IsUp = true;
      return (dirBits);
    }

    public void sub_F5A8_SetOpcodesAndVelocityFromDir (int pOpcodesList, int velocity) {
      _pOpcodes = (int) Game.AtariDisk.ReadLong (Game.AtariRamToDisk (pOpcodesList + _dir * 4));
      if (_pOpcodes == 0)
        throw new Exception ();
      _velocityXY = Match.GetVelocityXYFromDir (_dir, velocity);
    }

    private void sub_FC3C_Think_Coins (Match match) {
      if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam1) <= 16)
        match._creditsEarnedTeam1 += match._coinsCredits;
      else {
        if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam2) <= 16)
          match._creditsEarnedTeam2 += match._coinsCredits;
        else
          return;
      }
      InitItemCoins ();
    }

    public void sub_D272_ExecuteOpcodeToken (Match match) {
      if (match.Bit7_IsClockPaused)
        return;
      if (_timer != 0) {
        _timer--;
        if (_timer != 0)
          return;
      }
      Bit7_IsInvisible = false;
      if (_pThink == 0xFC9C)
        sub_FC9C_Think_Token (match);
      else
        throw new Exception ();
    }

    private void sub_FC9C_Think_Token (Match match) {
      Player player;
      if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam1) <= 16)
        player = match.PlayerHavingInputTeam1;
      else
        if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam2) <= 16)
          player = match.PlayerHavingInputTeam2;
        else
          return;

      int functionIndex = _spriteIndex - 55;
      if (functionIndex == 0)
        match._token.Init_FreezeTeam (match, this, player);
      else if (functionIndex == 1)
        match._token.Init_ReverseJoystick (match, this, player);
      else if (functionIndex == 2)
        match._token.Init_ReduceTeam (match, this, player);
      else if (functionIndex == 3)
        match._token.Init_IncreaseTeam (match, this, player);
      else if (functionIndex == 4)
        match._token.Init_Manic (match, this, player);
      else if (functionIndex == 5)
        match._token.Init_SlowTeam (match, this, player);
      else if (functionIndex == 6)
        match._token.Init_GrabBall (match, this, player);
      else if (functionIndex == 7)
        match._token.Init_Transport (match, this, player);
      else if (functionIndex == 8)
        match._token.Init_GoalDoor (match, this, player);
      else if (functionIndex == 9)
        match._token.Init_Shield (match, this, player);
      else if (functionIndex == 10)
        match._token.Init_FullEnergy (match, this, player);
      else if (functionIndex == 11)
        match._token.Init_ZapTeam (match, this, player);
      else if ((functionIndex >= 12) &&
               (functionIndex <= 14)) {
      }
      else
        throw new Exception ();
      InitItemToken (match.GameType);
    }

    public void sub_D2A4_ExecuteOpcodeEquipment (Match match) {
      if (match._equipmentOnFloorSpriteIndex == 0)
        return;
      if (match.Bit7_IsClockPaused)
        return;
      if (_timer != 0) {
        _timer--;
        if (_timer != 0)
          return;
      }
      Bit7_IsInvisible = false;
      if (_pThink == 0xFD92)
        sub_FD92_Think_Equipment (match);
      else
        throw new Exception ();
    }

    public void sub_FD92_Think_Equipment (Match match) {
      Player player;
      if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam1) <= 16)
        player = match.PlayerHavingInputTeam1;
      else
        if (CalcDistanceToEntity_Lesser16 (match.PlayerHavingInputTeam2) <= 16)
          player = match.PlayerHavingInputTeam2;
        else
          return;
      sub_FEE4_EquipEquipment (match, player);
    }

    private void sub_FEE4_EquipEquipment (Match match, Player player) {
      Bit7_IsInvisible = true;
      match._equipmentOnFloorSpriteIndex = 0;
      player._equipmentSpriteIndex = _spriteIndex;
      int equipmentIndex = _spriteIndex - 80;
      int attributeIndex = GetAttributeIndexFromEquipment (equipmentIndex) - 72;
      if (attributeIndex == 0)
        EquipEquipment_Helper (ref player._attributes._agr, ref player._agrTemp, player);
      else if (attributeIndex == 1)
        EquipEquipment_Helper (ref player._attributes._att, ref player._attTemp, player);
      else if (attributeIndex == 2)
        EquipEquipment_Helper (ref player._attributes._def, ref player._defTemp, player);
      else if (attributeIndex == 3)
        EquipEquipment_Helper (ref player._attributes._spd, ref player._spdTemp, player);
      else if (attributeIndex == 4)
        EquipEquipment_Helper (ref player._attributes._thr, ref player._thrTemp, player);
      else if (attributeIndex == 5)
        EquipEquipment_Helper (ref player._attributes._pow, ref player._powTemp, player);
      else if (attributeIndex == 6)
        EquipEquipment_Helper (ref player._attributes._sta, ref player._staTemp, player);
      else if (attributeIndex == 7)
        EquipEquipment_Helper (ref player._attributes._int, ref player._intTemp, player);
      else
        throw new Exception ();

      match._equipmentNbPickupRemaining--;
    }

    private static void EquipEquipment_Helper (ref int attribute, ref int attributeTemp, Player player) {
      if (attributeTemp != 0) {
        player._equipmentBackupAttributeTemp = attribute;
        player._equipmentBackupAttribute = attributeTemp;
        attributeTemp = 250;
      }
      else
        player._equipmentBackupAttribute = attribute;
      attribute = 250;
    }

    private int CalcDistanceToEntity_Lesser16 (Player player) {
      if (player.Bit4_IsFalling)
        return (17);
      if (player.Bit1_IsJumping)
        return (17);
      int deltaX = Math.Abs (_terrainXY.X - player._terrainXY.X);
      if (deltaX > 16)
        return (17);
      int deltaY = Math.Abs (_terrainXY.Y - player._terrainXY.Y);
      if (deltaY > 16)
        return (17);
      Point vector;
      return (CalcVector (player._terrainXY, out vector));
    }

    public int UnkInRect (Point xy, int distance) {
      int deltaX = Math.Abs (xy.X - _terrainXY.X);
      if (deltaX > distance)
        return (deltaX);
      int deltaY = Math.Abs (xy.Y - _terrainXY.Y - _originXY.Y);
      return (deltaY);
    }

    public bool IsOnscreen_Margin16 (Point scrollXY) {
      int x = _terrainXY.X - scrollXY.X;
      int y = _terrainXY.Y - scrollXY.Y;
      if ((y >= 16) && ((y <= 168)))
        if ((x >= 16) && ((x <= 304)))
          return (true);
      return (false);
    }

    public int CalcVector (Point xy, out Point vector) {
      int d0 = Math.Abs (_terrainXY.X - xy.X);
      int d3 = Math.Abs (_terrainXY.Y + _originXY.Y - xy.Y);
      vector = new Point (d0, d3);
      return (CalcVectorLength (vector));
    }

    public DirBits GetDirBitsToTargetBetter (Point target) {
      DirBits dirBits = new DirBits ();
      int deltaX = target.X - _terrainXY.X;
      int deltaY = target.Y - _terrainXY.Y;
      int deltaXAbs = Math.Abs (deltaX) / 2;
      int deltaYAbs = Math.Abs (deltaY) / 2;
      if (deltaY > deltaXAbs)
        dirBits.Bit1_IsDown = true;
      if (deltaY < -deltaXAbs)
        dirBits.Bit0_IsUp = true;
      if (deltaX > deltaYAbs)
        dirBits.Bit3_IsRight = true;
      if (deltaX < -deltaYAbs)
        dirBits.Bit2_IsLeft = true;
      return (dirBits);
    }

    public static int GetAttributeIndexFromEquipment (int equipmentIndex) {
      // BUGGED
      int offset = 0x2FC5 + equipmentIndex;
      return (Game.AtariDisk.ReadByte (Game.AtariRamToDisk (offset)));
    }

    public static int GetDirFromDirBits (DirBits dirBits) {
      int index = dirBits.Data;
      int offset = 0x4BC + index;
      int dir = Game.AtariDisk.ReadByteSigned (Game.AtariRamToDisk (offset));
      return (dir);
    }

    private static int CalcVectorLength (Point vectorXY) {
      int a = vectorXY.X;
      int b = vectorXY.Y;
      if (a < b)
        ExtensionMethods.Swap (ref a, ref b);
      int d1 = a;
      a = b;
      a >>= 1;
      d1 += a;
      a >>= 1;
      b <<= 1;
      if (d1 >= b) {
        b <<= 1;
        if (d1 >= b) {
          b = a;
          b >>= 1;
          a += b;
        }
        d1 -= a;
      }
      else {
        a >>= 1;
        d1 -= a;
      }
      return (d1);
    }

    public static int Size {
      get {
        return (0x28);
      }
    }

    public void Dump () {
      if (!(this is Player))
        Console.WriteLine ("Entity: {0:X8}", Game.AtariDiskToRam (_offset));
      Console.Write ("{0:X8} ", _pGfx);
      Console.Write ("{0:X8}  <- Entity", _pFunctionDraw);
      Console.WriteLine ();
      Console.Write ("{0:X8} ", _pThink);
      Console.Write ("{0:X8} ", _pOpcodes);
      Console.WriteLine ();
      Console.Write ("{0:X4} ", _width);
      Console.Write ("{0:X4} ", _height);
      Console.Write ("{0:X4} ", _terrainXY.X);
      Console.Write ("{0:X4} ", _terrainXY.Y);
      Console.WriteLine ();
      Console.Write ("{0:X4} ", _velocityXY.X);
      Console.Write ("{0:X4} ", _velocityXY.Y);
      Console.Write ("{0:X4} ", _opcodeIndex);
      Console.Write ("{0:X2} ", _dir);
      Console.Write ("{0:X2} ", _timer);
      Console.WriteLine ();
      Console.Write ("{0:X4} ", _spriteIndex);
      Console.Write ("{0:X2} ", _flags.Data);
      Console.Write ("{0:X2} ", _field_23_b);
      Console.Write ("{0:X4} ", _originXY.X);
      Console.Write ("{0:X4} ", _originXY.Y);
      Console.WriteLine ();
    }
  }

  public enum SideEnum {
    Red_0 = 0,
    Blue_1 = 1
  }
}
